前一篇實作了 Selenium 爬取 Dcard 文章的爬蟲,可以看到會出現一個瀏覽器視窗模擬使用者,並使用內建的選擇器鎖定文章資訊。
首先,Dcard 的設計是不會一次將所有文章皆讀取完成,而是每次使用者捲動到底部時讀取新的文章,因此如果要寫爬取 Dcard 的爬蟲(不使用 Dcard API)需要模擬使用者捲動螢幕。
今天要帶各位加上的功能是模擬使用者滾動螢幕,當爬取完成目前的所有內容後,自動滾動螢幕到最底部,之後再進行爬取,以此達到取得 Dcard 上文章的目的。
首先,我們先實作使用者輸入捲動次數的程式,並完成初步 Selenium 程式。
from selenium import webdriver
from time import sleep
import json
if __name__ == '__main__':
scroll_time = int(input('請輸入想要捲動幾次'))
driver = webdriver.Chrome()
driver.get('https://www.dcard.tw/f')
接下來,我們要完成程式捲動的部分。
在 Selenium 中支援執行 JavaScript 的功能,因此我們能透過 JavaScript 的 window.scrollTo
來達到捲動螢幕的效果。
from selenium import webdriver
from time import sleep
import json
if __name__ == '__main__':
scroll_time = int(input('請輸入想要捲動幾次'))
driver = webdriver.Chrome()
driver.get('https://www.dcard.tw/f')
sleep(2)
js = "window.scrollTo(0, document.body.scrollHeight);"
driver.execute_script(js)
完成捲動螢幕後,我們寫一個 for-loop 執行使用者輸入的次數。
from selenium import webdriver
from time import sleep
import json
if __name__ == '__main__':
scroll_time = int(input('請輸入想要捲動幾次'))
driver = webdriver.Chrome()
driver.get('https://www.dcard.tw/f')
for now_time in range(1, scroll_time+1):
sleep(2)
print(f"now scroll {now_time}/{scroll_time}")
js = "window.scrollTo(0, document.body.scrollHeight);"
driver.execute_script(js)
接下來,我們能將昨天實作的文章爬取寫入到迴圈當中。
有時會遇到無法找尋到元素的問題(文章刪除或廣告),因此我們能在爬取文章資訊的迴圈中加個 try-except,來讓程式不會因此停止。
from selenium import webdriver
from time import sleep
import json
if __name__ == '__main__':
scroll_time = int(input('請輸入想要捲動幾次'))
driver = webdriver.Chrome()
driver.get('https://www.dcard.tw/f')
results = []
for now_time in range(1, scroll_time+1):
sleep(2)
eles = driver.find_elements_by_class_name('tgn9uw-0')
for ele in eles:
try:
title = ele.find_element_by_class_name('tgn9uw-3').text
href = ele.find_element_by_class_name(
'tgn9uw-3').get_attribute('href')
subtitle = ele.find_element_by_class_name('tgn9uw-4').text
result = {
'title': title,
'href': href,
'subtitle': subtitle
}
results.append(result)
except:
pass
print(f"now scroll {now_time}/{scroll_time}")
js = "window.scrollTo(0, document.body.scrollHeight);"
driver.execute_script(js)
with open('Dcard-articles.json', 'w', encoding='utf-8') as f:
json.dump(results, f, indent=2,
sort_keys=True, ensure_ascii=False)
driver.quit()
這樣會有一個明顯的問題,會導致有些文章會重複爬取,如下圖。
解決這個問題也非常容易,只要記錄上一次爬取的最後一個文章元素,之後下次爬取時將那個元素之前的元素刪除即可(或者只進行爬取那個元素之後的元素)
from selenium import webdriver
from time import sleep
import json
if __name__ == '__main__':
scroll_time = int(input('請輸入想要捲動幾次'))
driver = webdriver.Chrome()
driver.get('https://www.dcard.tw/f')
results = []
prev_ele = None
for now_time in range(1, scroll_time+1):
sleep(2)
eles = driver.find_elements_by_class_name('tgn9uw-0')
# 若串列中存在上一次的最後一個元素,則擷取上一次的最後一個元素到當前最後一個元素進行爬取
try:
# print(eles)
# print(prev_ele)
eles = eles[eles.index(prev_ele):]
except:
pass
for ele in eles:
try:
title = ele.find_element_by_class_name('tgn9uw-3').text
href = ele.find_element_by_class_name(
'tgn9uw-3').get_attribute('href')
subtitle = ele.find_element_by_class_name('tgn9uw-4').text
result = {
'title': title,
'href': href,
'subtitle': subtitle
}
results.append(result)
except:
pass
prev_ele = eles[-1]
print(f"now scroll {now_time}/{scroll_time}")
js = "window.scrollTo(0, document.body.scrollHeight);"
driver.execute_script(js)
with open('Dcard-articles.json', 'w', encoding='utf-8') as f:
json.dump(results, f, indent=2,
sort_keys=True, ensure_ascii=False)
driver.quit()
到目前為止,功能已完成。
今天達成了在 Selenium 中執行 JavaScript 語句達成捲動螢幕的效果,並透過擷取串列達到不重複爬取的效果。
明天會帶各位實戰 Instagram 上自動點擊好友貼文讚的爬蟲。
Dcard : https://www.dcard.tw/f
Selenium with Python docs : https://selenium-python.readthedocs.io/
Selenium docs : https://readthedocs.org/projects/selenium-python/downloads/pdf/latest/